import type { Cell, Node, Model, CellView } from '@antv/x6';
import { Graph } from '@antv/x6';
import React, {
  useEffect,
  useRef,
  useState,
  useCallback,
  useMemo,
  useImperativeHandle,
  forwardRef,
} from 'react';
import {
  DeleteOutlined,
  FullscreenOutlined,
  FullscreenExitOutlined,
  RedoOutlined,
  UndoOutlined,
  ZoomInOutlined,
  ZoomOutOutlined,
} from '@ant-design/icons';
import type { ReactNode } from 'react';
import { message } from 'antd';
import Hierarchy from '@antv/hierarchy';
import {
  get,
  cloneDeep,
  dropRight,
  last,
  map,
  concat,
  filter,
  debounce,
  find,
  assign,
  isEqual,
} from 'lodash';
import { useHistoryTravel } from 'ahooks';
import { formatData, generateModal, initNode } from './util';
import { Toolbar } from '@antv/x6-react-components';
import './app.css';
import '@antv/x6-react-components/es/toolbar/style/index.css';

const { Item } = Toolbar;
const { Group } = Toolbar;

export interface ISingleNode {
  shape: string;
  x: number;
  y: number;
  width: number;
  height: number;
  label: string;
  data: any;
}

export interface IMindProps {
  data: { originData: any; singleNodes: ISingleNode[] };
  renderPanel?: ReactNode;
  onDataChange?: (data: any, singleNodes: any) => void;
  saveGraph?: (data: any) => void;
  renderEditPanel?: (nodeData: any, onChange: (v: any) => void) => ReactNode;
  preview?: boolean;
}

const BrainMap = (
  {
    data,
    renderPanel,
    onDataChange,
    saveGraph,
    renderEditPanel,
    preview,
  }: IMindProps,
  ref: any,
) => {
  const {
    value: history,
    setValue: setHistory,
    backLength,
    forwardLength,
    back,
    forward,
  } = useHistoryTravel<any>(data);
  const [node, setNode] = useState<Cell | null>(null);
  const { originData, singleNodes } = data;
  const treeData = formatData(originData);
  const graphRef = useRef<Graph | null>(null);
  const selectedNodeRef = useRef<Cell | null>(null);
  const containerRef = useRef<HTMLDivElement | null>(null);
  const [fullscreen, setFullscreen] = useState(false);

  const handleFullscreenChange = (fs: boolean) => {
    setFullscreen(fs);
  };

  const selectedNodeData = useMemo(() => {
    if (!node) return {};
    return node.getData() as any;
  }, [node]);

  const addNode = useCallback(
    (isChild = false) => {
      const currentNode = selectedNodeRef.current;
      const currentData = currentNode?.getData();
      if (currentData.singleNode) {
        return; // 这里是否是singleNodes
      }
      if (currentNode) {
        const ids = currentNode.id.split('-');
        const newData = cloneDeep(treeData);
        let current = newData[0];
        const len = isChild ? ids.length : ids.length - 1;
        for (let i = 1; i < len; i++) {
          const id = Number(ids[i]);
          if (current.children) {
            current = get(current, ['children', id]);
          }
        }
        if (!current.children) current.children = [];
        current.children.push({
          is: isChild
            ? `${current.id}-${current.children.length + 1}`
            : ['0', ...dropRight(ids, 1), current.children.length].join('-'),
          side: 'left',
          code: 'totalPL',
          name: '',
        });
        onDataChange?.(newData, singleNodes);
      }
    },
    [treeData],
  );

  const deleteNode = () => {
    const currentNode = selectedNodeRef.current;
    if (!currentNode) return;
    const currentData = currentNode.getData();
    if (currentData.singleNode) {
      const { index } = currentData;
      const newSingleNodes = filter(
        singleNodes,
        (item) => item.data.index !== index,
      );
      selectedNodeRef.current = null;
      setNode(null);
      onDataChange?.(treeData, newSingleNodes);
      return;
    }
    if (currentNode.id === '0') {
      message.error('根节点不允许删除');
      return;
    }
    const ids = currentNode.id.split('-');
    ids.shift();
    const newData = cloneDeep(treeData);
    let current = newData[0];
    const len = ids.length - 1;
    for (let i = 0; i < len; i++) {
      const id = Number(ids[i]);
      if (current.children) {
        current = get(current, ['children', id]);
      }
    }
    current.children.splice(last(ids), 1);
    setNode(null);
    selectedNodeRef.current = null;
    onDataChange?.(newData, singleNodes);
  };

  const scale = (isBig = true) => {
    graphRef.current?.zoom(isBig ? 0.2 : -0.2);
  };

  const onChange = (v: any) => {
    const currentNode = selectedNodeRef.current;

    if (!currentNode) return;
    if (selectedNodeData.singleNode) {
      const index = selectedNodeData?.index;

      const newSingleNodes = map(singleNodes, (item, i) => {
        return i !== index
          ? item
          : {
              ...item,
              label: v?.label || item.label,
              data: {
                ...item.data,
                ...v,
              },
            };
      });
      onDataChange?.(treeData, newSingleNodes);
      return;
    }
    const ids = currentNode.id.split('-');

    const newData = cloneDeep(treeData);
    let current = newData[0];

    const len = ids.length;
    for (let i = 1; i < len; i++) {
      const id = Number(ids[i]);
      if (current.children) {
        current = get(current, ['children', id]);
      }
    }

    assign(current, v);

    onDataChange?.(newData, singleNodes || []);
  };

  useEffect(() => {
    // 创建画布
    const graph = new Graph({
      container: containerRef.current as HTMLElement,
      grid: true,
      scroller: !preview,
      // snapline: true , // 对齐线
      autoResize: true,
      keyboard: true,
      interacting(cellView: CellView) {
        const nodeData = cellView.cell.getData();
        if (nodeData?.singleNode) {
          return true;
        }
        return false;
      },
      history: {
        enabled: true,
      },
      selecting: preview
        ? false
        : {
            enabled: true,
            showNodeSelectionBox: true,
          },
      connecting: {
        router: {
          name: 'manhattan',
          args: {
            step: 30,
          },
        },
      },
    });

    graphRef.current = graph;

    const result = Hierarchy.mindmap(treeData[0] || {}, {
      direction: 'V',
      // 节点高度
      getHeight() {
        return 60;
      },
      // 节点宽度
      getWidth() {
        return 150;
      },
      // 节点水平间隔
      getHGap() {
        return 70;
      },
      getVGap() {
        return 30;
      },
      getSide() {
        return 'right';
      },
    });

    const model = generateModal(result);

    graph.fromJSON(model);
    graph.zoomTo(0.8);
    graph.centerContent();

    map(singleNodes, (item, index) => {
      const sNode = get(singleNodes, [index]);
      if (sNode) {
        graph.addNode(sNode);
      }
    });

    if (selectedNodeRef.current) {
      const nodes = graph.getNodes();
      const current = find(nodes, (cell) => {
        const nodeData = cell.getData();
        return nodeData?.singleNode
          ? nodeData.index === selectedNodeData.index
          : nodeData?.id === selectedNodeData.id;
      });
      const cell = graph.getCellById(current?.id as string);
      graph.resetSelection(cell);
      selectedNodeRef.current = cell;
      setNode(cell);
    }

    graph.on(
      'node:selected',
      (args: { cell: Cell; node: Node; options: Model.SetOptions }) => {
        selectedNodeRef.current = args.cell;
        setNode(args.cell);
        const nodeData = args.cell.getData();
        if (nodeData.singleNode) {
          graph.enableSelectionMovable();
        } else {
          graph.disableSelectionMovable();
        }
      },
    );

    const updateCellInfo = debounce((cell: Cell) => {
      const nodeData = cell.getData();
      const pos = cell.getProp<{ x: number; y: number }>('postion');
      onDataChange?.(
        treeData,
        map(singleNodes, (item, i) => {
          return i === nodeData?.index
            ? {
                ...item,
                ...pos,
              }
            : item;
        }),
      );
    }, 500);

    // graph.on('cell:changed', ({ cell }) => {
    //   console.log('我触发了');

    //   updateCellInfo(cell);
    // });

    graph.on('blank:dblclick', ({ x, y }) => {
      const newNode = {
        shape: 'rect',
        x,
        y,
        width: 150,
        height: 40,
        label: initNode.name,
        data: {
          ...initNode,
          singleNode: true,
          index: singleNodes.length ? last(singleNodes)?.data?.index + 1 : 0,
        },
      };
      onDataChange?.(treeData, concat(filter(singleNodes, Boolean), newNode));
    });

    graph.bindKey('tab', () => {
      addNode(true);
    });

    graph.bindKey('enter', () => {
      addNode(false);
    });

    graph.bindKey('delete', () => {
      deleteNode();
    });

    graph.bindKey('backspace', () => {
      deleteNode();
    });

    saveGraph?.(graphRef.current?.toJSON());

    if (!isEqual(history, data)) {
      setHistory(data);
    }

    return () => {
      graph.dispose();
      graphRef.current = null;
    };
  }, [data]);

  useEffect(() => {
    if (!isEqual(history, data)) {
      onDataChange?.(history.originData, history.singleNodes);
    }
  }, [history]);

  useImperativeHandle(ref, () => ({
    graph: graphRef.current,
  }));

  return (
    <div
      style={{
        width: '100%',
        userSelect: 'none',
        height: '100%',
      }}
    >
      <div
        style={{
          display: 'flex',
          position: 'relative',
          height: '100%',
        }}
      >
        <div style={{ flex: 1, height: '100%' }}>
          <div ref={ref} style={{ width: '100%', height: '100%' }}>
            <Toolbar className={'fincore-mindeditor-toolbar'}>
              <Group>
                <Item
                  name="zoomIn"
                  onClick={() => scale()}
                  tooltip="放大 (Cmd +)"
                  icon={<ZoomInOutlined />}
                />
                <Item
                  name="zoomOut"
                  onClick={() => scale(false)}
                  tooltip="缩小 (Cmd -)"
                  icon={<ZoomOutOutlined />}
                />
                {fullscreen ? (
                  <Item
                    name="FullScreen"
                    onClick={() => {
                      setFullscreen(false);
                    }}
                    tooltip="退出全屏"
                    icon={<FullscreenExitOutlined />}
                  />
                ) : (
                  <Item
                    name="FullScreen"
                    onClick={() => {
                      setFullscreen(true);
                    }}
                    tooltip="全屏"
                    icon={<FullscreenOutlined />}
                  />
                )}
              </Group>
              {preview ? null : (
                <>
                  <Group>
                    <Item
                      name="undo"
                      disabled={backLength < 2}
                      onClick={back}
                      tooltip="撤销 (Cmd + Z)"
                      icon={<UndoOutlined />}
                    />
                    <Item
                      name="redo"
                      disabled={!forwardLength}
                      onClick={forward}
                      tooltip="重做 (Cmd + shift + Z)"
                      icon={<RedoOutlined />}
                    />
                  </Group>
                  <Group>
                    <Item
                      name="delete"
                      onClick={deleteNode}
                      disabled={!selectedNodeRef.current}
                      tooltip="删除 (Delete)"
                      icon={<DeleteOutlined />}
                    />
                  </Group>
                </>
              )}
            </Toolbar>
            <div
              style={{
                width: '100%',
                height: '100%',
                flex: 1,
                display: 'flex',
              }}
            >
              <div
                ref={containerRef}
                style={{ width: '100%', height: '100%', flex: 1 }}
              />
            </div>
          </div>
        </div>
        {node && selectedNodeData ? (
          <div>{renderEditPanel?.(selectedNodeData, onChange)}</div>
        ) : (
          renderPanel
        )}
        <div style={{ position: 'absolute', bottom: 20, left: 20 }}>
          回车：增加同级节点&nbsp;&nbsp; Tab: 添加子节点&nbsp;&nbsp;delete |
          backspace: 删除节点
        </div>
      </div>
    </div>
  );
};

export default forwardRef(BrainMap);
